
//License Information
/*
    Copyright 2010 Jozsef Jantek

    This file is part of HunterTC.

    HunterTC is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    HunterTC is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with HunterTC.  If not, see <http://www.gnu.org/licenses/>.
*/

package huntertc;

import java.util.*;

public class Combat_Log
{
    public int cl_miss=0, cl_crit=0, cl_hit=0, cl_ev=0;
    public int rand_total=0, opt_dam=0, opt_dam_dr=0;
    public int cl_time=0, armor=0;
    private int fight_time=0, latency=20;
    public float rating_hit=(float)120.1, rating_crit=(float)179.28, rating_haste=(float)128.05;
    public boolean write_log=true, procs=true;
    public Combat_Event events= new Combat_Event();
    public Combat_Event chain= new Combat_Event();
    public Char_sheet chr=new Char_sheet();
    public String output="", aep="", summary="";
    //Shot setup
    Shot_Auto s_auto= new Shot_Auto(chr);
    Shot_Steady s_steady= new Shot_Steady(chr);
    Shot_Explosive s_exp= new Shot_Explosive(chr);
    Shot_Black s_black= new Shot_Black(chr);
    Shot_Serpent s_serp= new Shot_Serpent(chr);
    Shot_Aimed s_aimed= new Shot_Aimed(chr);
    Shot_Arcane s_arcane= new Shot_Arcane(chr);
    Shot_Chimera s_chimera= new Shot_Chimera(chr);
    //Proc Shots
    public Shot s_wildq= new Shot();
    public Shot s_chim_serp= new Shot();
    public Shot s_piercing= new Shot();


    Combat_Log(Char_sheet schar)
    {
        chr=schar;
        chr.pr[0]=s_wildq;
        s_wildq.name="Wild Quiver";
        s_wildq.school=3;
        WildqInit();
        chr.pr[1]=s_chim_serp;
        s_chim_serp.name="Chimera Serpent";
        s_chim_serp.school=3;
        s_chim_serp.crit_dmg=2*chr.t.MM[27].val;
        chr.pr[2]=s_piercing;
        s_piercing.name="Piercing Shot";
        s_piercing.school=5;
        s_piercing.tick=8;
        s_piercing.tick_rate=1;
        s_piercing.bon_dmg=(chr.b_Trauma?30:0);
    }

    public void setFight_time(int time){fight_time=time*100;}

    public void setLatency(int time){latency=time/10;}

    public float FormatFloat(float s) {return (float)((int)(s*100))/100;}

    public String Fight(boolean log, boolean proc)
    {
        write_log=log;
        procs=proc;
        if(write_log) output="";
        if(procs) cl_miss=cl_crit=cl_hit=cl_ev=0;
        if(procs) rand_total=0;
        if(procs) for(int i=0;i<10;i++) chr.s[i].dmg_done=chr.s[i].ev=chr.s[i].avg_hit=chr.s[i].ev_hit=chr.s[i].avg_crit=chr.s[i].ev_crit=0;
        if(procs) for(int i=0;i<3;i++) chr.pr[i].dmg_done=chr.pr[i].ev=chr.pr[i].avg_hit=chr.pr[i].ev_hit=chr.pr[i].avg_crit=chr.pr[i].ev_crit=0;
        opt_dam=opt_dam_dr=cl_time=0;
        chr.b_imphawk=chr.b_impsteady=chr.b_serpent=chr.b_black=chr.b_tactician=chr.b_expose=chr.b_locknload=false;

        if(chr.t.SV[26].val>0) chr.b_sniper=true;
        else chr.b_sniper=false;

        s_piercing.bon_dmg=(chr.b_Trauma?30:0);

        //Optimized Master Tactician
        if(!procs && chr.t.SV[22].val>0) chr.b_tactician_opt=true;
        else chr.b_tactician_opt=false;

        //Optimized Improved Hawk
        if(!procs && chr.t.BM[1].val>0) chr.b_imphawk_opt=true;
        else chr.b_imphawk_opt=false;

        //Optimized Expose Weakness
        if(!procs && chr.t.SV[19].val>0) chr.b_expose_opt=true;
        else chr.b_expose_opt=false;

        for(int i=0;i<10;i++) chr.s[i].Calc(chr);

        Events();
                
        return output;
    }

    public void Events()
    {
        cl_time=0;
        events.Reset();
        chain.Reset();
        
        //Initialize Rotation
        for(int i=0;i<10;i++) if(chr.s[i].name!="Combat") chain.AddShotEvent(i*10,chr.s[i]);
        
        Combat_Event read= new Combat_Event();
        Combat_Event per= new Combat_Event();

        //Initialize Combat Log
        if(write_log)
        {
            output+="*****Combat*****\n\n";
            output+=Time(0)+"Combat has started!\n";
        }

        //Build the chain
        while(cl_time<fight_time)
        {
            read=chain.next;
            per=events.next;

            //Find available shot
            while(read.time>cl_time) 
            {
                if(read.next.shot.name=="Combat") 
                {
                    cl_time+=10;
                    read=read.next.next;
                }
                else read=read.next;    
            }

            //Find next event
            if(per.shot.name!="Combat" && per.time<=cl_time && (read.shot.name!="Auto Shot" || per.time<=read.time))
                {
                    if(per.shot.name=="Hawk" && procs) HawkFade();
                    else if(per.shot.name=="Serpent" && procs) SerpentFade();
                    else if(per.shot.name=="Black" && procs) BlackFade();
                    else if(per.shot.name=="Tactician" && procs) TactFade();
                    else if(per.shot.name=="Expose" && procs) ExpoFade();
                    else{
                        int s=Damage(per);
                        Proc(per,s);
                    }

                    per.DelEvent();
                }
            else
                {
                    read.shot.Calc(chr);
                    if(read.shot.name=="Auto Shot")
                    {
                        int s=Damage(read);
                        Proc(read,s);
                        read.time+=(int)(read.shot.getAct_cast()*100);

                    }
                    else if(read.shot.tick>1 && read.shot.name!="Explosive Shot")
                    {
                        Random generator = new Random();
                        int chance= generator.nextInt(10000);
                        if(procs && chance<chr.getMiss()*100) {if(write_log) output+=Time(cl_time)+read.shot.name+" missed.\n";}
                        else {
                        if(write_log) output+=Time(cl_time)+read.shot.name+" applied.\n";
                        for(int i=1;i<=read.shot.tick;i++) events.AddDotEvent(cl_time+read.shot.tick_rate*100*i,read.shot);
                        if(read.shot.name=="Serpent Sting") SerpentApply(read);
                        if(read.shot.name=="Black Arrow") BlackApply(read);
                        read.time=(int)(cl_time+read.shot.getAct_cast()*100);
                        }
                        cl_time+=read.shot.gcd+latency;
                    }
                    else
                    {
                        read.time=cl_time;
                        int s=Damage(read);
                        Proc(read,s);
                        if(read.shot.name=="Explosive Shot") for(int i=1;i<=read.shot.tick;i++) events.AddDotEvent(read.time+read.shot.tick_rate*100*i,read.shot);
                        read.time=(int)(cl_time+read.shot.getAct_cast()*100);
                        cl_time+=read.shot.gcd+latency;
                    }
                }
        }
    }

    public void Proc(Combat_Event e, int s)
    {
            //No Miss
            if(s!=0)
            {
                //Improved Aspect of the Hawk
                if(procs && chr.b_Hawk && chr.t.BM[1].val>0 && (e.shot.tick==1 || e.shot.name=="Explosive Shot"))
                {
                    Random procc = new Random();
                    int proc= procc.nextInt(100);
                    if(proc<10)
                    {
                        chr.b_imphawk=true;
                        if(write_log) output+=Time(e.time)+"Improved Aspect of the Hawk was triggered.\n";
                        Shot hawk= new Shot();
                        hawk.name="Hawk";
                        events.AddShotEvent(e.time+1200, hawk);
                    }
                }

                //Wild Quiver
                if(procs && e.shot.name=="Auto Shot")
                {
                    WildqInit();
                    Combat_Event w= new Combat_Event(e.time,s_wildq);

                    Random procc = new Random();
                    int proc= procc.nextInt(100);
                    if(proc<chr.t.MM[23].val*4) Damage(w);
                }

                //Chimera Sting
                if(e.shot.name=="Chimera Shot")
                {
                    //Delete existing serpent sting dots
                    Combat_Event temp= events.next;
                    boolean r=false;
                    while(temp.shot.name!="Combat"){
                        if(temp.shot.name=="Serpent Sting" || temp.shot.name=="Serpent") {temp.DelEvent(); r=true;}
                        temp=temp.next;
                    }

                    if(r)
                    {
                        //Chimera Serpent damage
                        s_chim_serp.avg_dmg=(int)((((float)s_serp.avg_dmg*(1+s_serp.getBon_dmg()))*s_serp.tick)*0.4);
                        s_chim_serp.Opt_Dmg(chr);

                        Combat_Event w= new Combat_Event(e.time,s_chim_serp);
                        Damage(w);

                        //Reapply Serpent Sting
                        for(int i=1;i<=s_serp.tick;i++) events.AddDotEvent(e.time+s_serp.tick_rate*100*i,s_serp);
                        if(write_log) output+=Time(e.time)+"Serpent Sting was reapplied.\n";
                        SerpentApply(e);

                        //Delay Serpent Sting cast
                        temp=chain.next;
                        while(temp.shot.name!="Serpent Sting") temp=temp.next;
                        temp.time=(int)(e.time+temp.shot.getAct_cast()*100);
                    }
                }

                //Improved Steady
                if(procs && e.shot.name=="Steady Shot" && chr.t.MM[25].val>0){
                    Random procc = new Random();
                    int proc= procc.nextInt(100);
                    if(proc<chr.t.MM[25].val*5){
                        if(write_log) output+=Time(e.time)+"Improved Steady Shot was triggered.\n";
                        chr.b_impsteady=true;
                    }
                }

                //Master Tactician
                if(procs && chr.t.SV[22].val>0){
                    Random procc = new Random();
                    int proc= procc.nextInt(100);
                    if(proc<10){
                        chr.b_tactician=true;
                        if(write_log) output+=Time(e.time)+"Master Tactician was triggered.\n";
                        Shot tact= new Shot();
                        tact.name="Tactician";
                        events.AddShotEvent(e.time+800, tact);
                    }
                }
            }
            //Crit
            if(s==2)
            {
                //Piercing Shots
                if(procs && chr.t.MM[18].val>0 && (e.shot.name=="Aimed Shot" || e.shot.name=="Steady Shot" || e.shot.name=="Chimera Shot"))
                {
                    //Delete existing dots
                    boolean r=false;
                    Combat_Event temp= events.next;
                    while(temp.shot.name!="Combat"){
                        if(temp.shot.name=="Piercing Shot") {temp.DelEvent(); r=true;}
                        temp=temp.next;
                    }
                    int temp_avg=(int)((float)(e.shot.FinalCrit(chr))*chr.t.MM[18].val/10/s_piercing.tick);
                    if(!r) {s_piercing.avg_dmg=temp_avg;}
                    else if(s_piercing.avg_dmg<temp_avg){s_piercing.avg_dmg=temp_avg;}


                    //Apply new dots
                    for(int i=1;i<=s_piercing.tick;i++) events.AddDotEvent(e.time+s_piercing.tick_rate*100*i,s_piercing);
                }

                //Expose Weakness
                if(procs && chr.t.SV[19].val>0)
                {
                    Random procc = new Random();
                    int proc= procc.nextInt(100);
                    if(proc<(100/(3/chr.t.SV[19].val))){
                        chr.b_expose=true;
                        if(write_log) output+=Time(e.time)+"Expose Weakness was triggered.\n";
                        Shot expo= new Shot();
                        expo.name="Expose";
                        events.AddShotEvent(e.time+700, expo);
                    }
                }
            }
    }

    public void HawkFade()
    {
        boolean r=false;
        Combat_Event temp= events.next.next;
        while(temp.shot.name!="Combat")
        {
            if(temp.shot.name=="Hawk") r=true;
            temp=temp.next;
        }
        if(!r)
        {
            chr.b_imphawk=false;
            if(write_log) output+=Time(events.next.time)+"Improved Aspect of the Hawk has faded.\n";
        }
    }

    public void TactFade()
    {
        boolean r=false;
        Combat_Event temp= events.next.next;
        while(temp.shot.name!="Combat")
        {
            if(temp.shot.name=="Tactician") r=true;
            temp=temp.next;
        }
        if(!r)
        {
            chr.b_imphawk=false;
            if(write_log) output+=Time(events.next.time)+"Master Tactician has faded.\n";
        }
    }

    public void ExpoFade()
    {
        boolean r=false;
        Combat_Event temp= events.next.next;
        while(temp.shot.name!="Combat")
        {
            if(temp.shot.name=="Expose") r=true;
            temp=temp.next;
        }
        if(!r)
        {
            chr.b_expose=false;
            if(write_log) output+=Time(events.next.time)+"Expose Weakness has faded.\n";
        }
    }

    public void SerpentApply(Combat_Event ev)
    {
        chr.b_serpent=true;
        if(write_log&&chr.t.SV[23].val>0) output+=Time(ev.time)+"Serpent Sting damage buff was triggered.\n";
        Shot serp= new Shot();
        serp.name="Serpent";
        events.AddShotEvent(ev.time+ev.shot.cooldown*100, serp);
    }

    public void SerpentFade()
    {
        boolean r=false;
        Combat_Event temp= events.next.next;
        while(temp.shot.name!="Combat")
        {
            if(temp.shot.name=="Serpent") r=true;
            temp=temp.next;
        }
        if(!r)
        {
            chr.b_serpent=false;
            if(write_log) output+=Time(events.next.time)+"Serpent Sting damage buff has faded.\n";
        }
    }

    public void BlackApply(Combat_Event ev)
    {
        chr.b_black=true;
        if(write_log) output+=Time(ev.time)+"Black Arrow damage buff was triggered.\n";
        Shot black= new Shot();
        black.name="Black";
        events.AddShotEvent(ev.time+ev.shot.cooldown*100, black);
    }

    public void BlackFade()
    {
        boolean r=false;
        Combat_Event temp= events.next.next;
        while(temp.shot.name!="Combat")
        {
            if(temp.shot.name=="Black") r=true;
            temp=temp.next;
        }
        if(!r)
        {
            chr.b_black=false;
            if(write_log) output+=Time(events.next.time)+"Black Arrow damage buff has faded.\n";
        }
    }

    public int Damage(Combat_Event read)
    {
        read.shot.Calc(chr);
        
        if(procs){
        int damage=read.shot.FinalDR(chr);
        int dmg=0;
        Random generator = new Random();
        int chance= generator.nextInt(10000);
        //Shots
        if(read.shott)
        {
        if(chance<(int)(chr.getMiss()*100)){
            damage=0;
            if(write_log) output+=Time(read.time)+read.shot.name+" missed.\n";
            cl_miss++;
        }
        else if(chance<(int)(chr.getMiss()*100+read.shot.getCrit(chr)*100) && read.shot.name!="Serpent Sting")
        {
            damage=read.shot.FinalCritDR(chr);
            if(write_log) output+=Time(read.time)+read.shot.name+" hits for "+damage+" critical damage.\n";
            cl_crit++;
            read.shot.ev_crit++;
            read.shot.avg_crit+=damage;
            dmg=2;
        }
        else{     
            if(write_log) output+=Time(read.time)+read.shot.name+" hits for "+damage+" damage.\n";
            cl_hit++;
            read.shot.ev_hit++;
            read.shot.avg_hit+=damage;
            dmg=1;
        }
        }
        //Dots
        else if(read.dot)
        {
        /*if(chance<(int)(chr.getCrit()*100)){
            damage=read.shot.FinalCrit(chr);
            if(write_log==true) output+=(read.shot.name+"'s dot hits for "+damage+" critical damage.\n");
            cl_crit++;
            dmg=2;
        }
        else{*/
            if(write_log) output+=Time(read.time)+read.shot.name+"'s dot hits for "+damage+" damage.\n";
            cl_hit++;
            dmg=1;
        }

        if(dmg!=0 && read.shot.name=="Arcane Shot" || read.shot.name=="Aimed Shot" || read.shot.name=="Chimera Shot")
        {
            if(write_log && chr.b_impsteady) output+=Time(read.time)+"Improved Steady Shot has faded.\n";
            chr.b_impsteady=false;
        }

        cl_ev++;
        rand_total+=damage;
        read.shot.ev++;
        read.shot.dmg_done+=damage;
        return dmg;
        }


        //Proc corrections
        if(!procs)
        {
            opt_dam+=read.shot.opt_dmg;
            opt_dam_dr+=read.shot.opt_dmg_dr;
            //Wild Quiver Optimal Damage
            if(read.shot.name=="Auto Shot")
            {
                WildqInit();
                opt_dam+=(int)(s_wildq.opt_dmg*(float)chr.t.MM[23].val*4/100);
                opt_dam_dr+=(int)(s_wildq.opt_dmg_dr*(float)chr.t.MM[23].val*4/100);
            }
            //Piercing Shots Optimal Damage
            if(read.shot.name=="Aimed Shot" || read.shot.name=="Steady Shot" || read.shot.name=="Chimera Shot")
            {
                opt_dam+=(int)((float)s_chimera.FinalCrit(chr)*chr.t.MM[18].val/10/s_piercing.tick*1.8*(1+s_piercing.getBon_dmg()));
                opt_dam_dr+=(int)((float)s_chimera.FinalCrit(chr)*chr.t.MM[18].val/10/s_piercing.tick*1.8*(1+s_piercing.getBon_dmg()));
            }
            //Improved Steady Shot
            if(read.shot.name=="Chimera Shot")
            {
                opt_dam+=(int)(read.shot.opt_dmg*(0.0225*chr.t.MM[25].val));
                opt_dam_dr+=(int)(read.shot.opt_dmg_dr*(0.0225*chr.t.MM[25].val));
            }
        }
        return 1;
    }

    public String Time(int tim)
    {
        String time="";
        int cl_min=tim/6000, cl_sec=tim-cl_min*6000;
        
        if(cl_min<10) time="0"+cl_min+":";
        else time+=cl_min+":";

        if(cl_sec<1000) time+="0"+(float)cl_sec/100+"  ";
        else time+=(float)cl_sec/100+"  ";

        return time;
    }

    public float OverallDPS(int dam)
    {
        float ovdps=(float)dam/(cl_time/100);
        return ovdps;
    }

    public void WildqInit()
    {
        s_wildq.avg_dmg=(int)(s_auto.avg_dmg*0.8);
        s_wildq.bon_dmg=(int)(chr.b_Elements?13:0);
        s_wildq.Opt_Dmg(chr);
    }

    public void AEP()
    {
        float default_dps=0, rap_dps=0, hit_dps=0, crit_dps=0, haste_dps=0, armp_dps=0;
        
        aep=("*****Agility Equivalence Points*****\n");

        //Default DPS
        Fight(false, false);
        default_dps=OverallDPS(opt_dam_dr);

        //Attack Power DPS
        chr.setBonusRAP(chr.getBonusRAP()+100);
        Fight(false, false);
        rap_dps=OverallDPS(opt_dam_dr);
        chr.setBonusRAP(chr.getBonusRAP()-100);
        
        //Hit chance DPS
        chr.setHit(chr.getHit()+1);
        Fight(false, false);
        hit_dps=OverallDPS(opt_dam_dr);
        chr.setHit(chr.getHit()-1);
        
        //Crit chance DPS
        chr.setBonusCrit(chr.getBonusCrit()+1);
        Fight(false, false);
        crit_dps=OverallDPS(opt_dam_dr);
        chr.setBonusCrit(chr.getBonusCrit()-1);
        
        //ArmorPen DPS
        chr.setArmp(chr.getArmp()+10);
        Fight(false, false);
        armp_dps=OverallDPS(opt_dam_dr);
        chr.setArmp(chr.getArmp()-10);

        //Haste DPS
        chr.setHaste(chr.getHaste()+10);
        Fight(false, false);
        haste_dps=OverallDPS(opt_dam_dr);
        chr.setHaste(chr.getHaste()-10);

        //Show
        aep+=("\n+1 Agility DPS increase: "+FormatFloat((float)((rap_dps-default_dps)/100+(crit_dps-default_dps)/83.33))+"\n");
        aep+=("+1 Attack Power DPS increase: "+FormatFloat((rap_dps-default_dps)/100)+"\n");
        aep+=("+1 Hit rating DPS increase: "+FormatFloat((float)((hit_dps-default_dps)/rating_hit))+"\n");
        aep+=("+1 Crit rating DPS increase: "+FormatFloat((float)((crit_dps-default_dps)/rating_crit))+"\n");
        aep+=("+1 Haste rating DPS increase: "+FormatFloat((float)((haste_dps-default_dps)/10/rating_haste))+"\n");
        //aep+=("+1 ArP rating DPS increase: "+FormatFloat((armp_dps-default_dps)/10/14)+"\n");
        aep+=("\nDefault DPS: "+FormatFloat(default_dps)+"\n");
        aep+=("\n+100 Attack Power: "+FormatFloat(rap_dps)+"\nDPS increase: "+FormatFloat(rap_dps-default_dps)+"\n");
        aep+=("\n+1% Hit chance: "+FormatFloat(hit_dps)+"\nDPS increase: "+FormatFloat(hit_dps-default_dps)+"\n");     
        aep+=("\n+1% Crit chance: "+FormatFloat(crit_dps)+"\nDPS increase: "+FormatFloat(crit_dps-default_dps)+"\n");
        aep+=("\n+10% Haste: "+FormatFloat(haste_dps)+"\nDPS increase: "+FormatFloat(haste_dps-default_dps)+"\n");
        //aep+=("\n+10% Armor Penetration: "+FormatFloat(armp_dps)+"\nDPS increase: "+FormatFloat(armp_dps-default_dps)+"\n");
    }
            
    
    public void Summarize()
    {
        summary=("*****Total*****\n");
        summary+=("\nTotal Damage: "+rand_total);
        for(int i=0;i<6;i++) if(chr.s[i].name!="Combat"){summary+="\n - "+FormatFloat(100/(float)rand_total*chr.s[i].dmg_done)+"%   "+chr.s[i].name+": "+chr.s[i].dmg_done;}
        if(chr.t.MM[23].val>0) summary+="\n - "+FormatFloat(100/(float)rand_total*s_wildq.dmg_done)+"%   "+s_wildq.name+": "+s_wildq.dmg_done;
        if(chr.t.MM[27].val>0) summary+="\n - "+FormatFloat(100/(float)rand_total*s_chim_serp.dmg_done)+"%   "+s_chim_serp.name+": "+s_chim_serp.dmg_done;
        if(chr.t.MM[18].val>0) summary+="\n - "+FormatFloat(100/(float)rand_total*s_piercing.dmg_done)+"%   "+s_piercing.name+": "+s_piercing.dmg_done;
        summary+=("\n\nTotal Time: "+Time(cl_time)+"\nOverall DPS: "+FormatFloat(OverallDPS(rand_total)));
        summary+=("\n\nOptimal Damage: "+opt_dam+"\nOptimal DPS: "+FormatFloat(OverallDPS(opt_dam)));
        summary+=("\n\nAfter Armor:\nOptimal Damage: "+opt_dam_dr+"\nOptimal DPS: "+FormatFloat(OverallDPS(opt_dam_dr))+"\n");
        summary+=("\nEvents: "+cl_ev+"\n- Miss: "+cl_miss+" ("+FormatFloat((float)cl_miss/cl_ev*100)+"%)"+"\n- Crit: "+cl_crit+" ("+FormatFloat((float)cl_crit/cl_ev*100)+"%)"+"\n- Hit: "+cl_hit+" ("+FormatFloat((float)cl_hit/cl_ev*100)+"%)\n");
    }

    
}
